Lab 01 - Pandas
Lab 01 - wstęp do pandas
W obrębie całego kursu do przechowywania i manipulacji danymi
będziemy wykorzystywać narzędzia dostarczane przez bibliotekę
pandas. Dla przypomnienia: pandas to biblioteka
służąca do zarządzania i przetwarzania danych tabelarycznych. Pozwala na
tworzenie dwuwymiarowych tabel z danymi wraz z nagłówkami i wygodną
manipulację nimi: m.in. wycinanie określonych fragmentów, sklejanie
wielu zestawów danych, interpolację czy import/eksport do wielu formatów
plików. Biblioteka jest mocno powiązana z NumPy, chociaż
przechowywane dane nie muszą być liczbami.
Stwórz projekt w PyCharm, utwórz nowy skrypt, w którym będziesz
testować poznawane narzędzia. Zaimportuj bibliotekę pandas
jako pd oraz numpy jako np. W
przypadku tych biblbiotek zawsze trzymaj się tej konwencji importu.
Klasa DataFrame
Podstawową klasą przechowującą dane jest pd.DataFrame.
Dane mają układ dwuwymiarowej tabeli, kolumny mogą mieć
nagłówki, a wiersze indeks. Stwórzmy przykładowy
DataFrame przechowujący losowe dane o pogodzie poprzez
przekazanie macierzy numpy jako danych, listy napisów jako
nagłówków kolumn oraz wygenerowanego zakresu dat jako
indeksu wierszy.
num_days = 20
temperature = np.random.uniform(20, 28, size=(num_days, 1))
pressure = np.random.uniform(990, 1010, size=(num_days, 1))
rain = np.random.uniform(0, 20, size=(num_days, 1))
random_data = np.hstack((temperature, pressure, rain))
df_weather = pd.DataFrame(index=pd.date_range("20200501", periods=num_days, freq="1D"),
data=random_data, columns=["Temperature", "Pressure", "Rain"])Przekazanie DataFrame do funkcji print()
wyświetli zawartość tabeli (jeśli wierszy/kolumn jest dużo, zostaną
wyświetlone tylko początkowe/końcowe):
print(df_weather)Możliwe jest też tworzenie DataFrame na bazie słownika,
którego klucze zostaną potraktowane jako nagłówki kolumn, a
wartości jako dane w danej kolumnie:
df_people = pd.DataFrame({"Height": [180, 160, 195],
"Weight": [77, 52, 200]})
print(df_people)Zauważ, że domyślne indeksy przyjmują wartości całkowite od 0 w górę.
Możliwe jest uzyskanie krótkiego podsumowania statystycznego tabeli
metodą describe() (zwracany jest nowy
DataFrame zawierający statystykę poszczególnych kolumn), a
także podgląd początku (head()) i końca
(tail()) tabeli.
df_weather_summary = df_weather.describe()
print(df_weather_summary)Wyświetlanie danych na wykresie
Biblioteka pandas bardzo dobrze współpracuje z biblioteką do
wyświetlania wykresów. Zaimportujmy matplotlib.pyplot jako
plt, a następnie wywołajmy polecenia:
df_weather.plot()
plt.show()Pandas automatycznie ustawi etykiety osi oraz wypełni legendę nagłówkami kolumn, a osią x będzie indeks wierszy.
Możemy również wykorzystać wykresy punktowe
(kind=scatter), słupkowe (kind=bar) lub
ręcznie podać kolumny dla osi:
df_weather.plot(kind='scatter', x='Temperature', y='Rain')
plt.show()Do zaawansowanego wyświetlania danych na wykresach powrócimy w kolejnych laboratoriach.
Podgląd
DataFrame w interfejsie graficznym -
pandasgui
W większości przypadków w zupełności wystarczy nam podgląd danych bezpośrednio w konsoli lub w komórce Jupytera, czasem jednak wygodniej jest przejrzeć dane w trybie okienkowym.
Zainstaluj pakiet pandasgui i zaimportuj go do skryptu.
Przykladowe użycie w trybie blokującym (wykonywanie programu zostanie
wstrzymane do czasu zamknięcia okna):
pandasgui.show(df_weather, settings={'block': True})Dostęp do danych, indeksowanie
Stwórzmy niewielki DataFrame do testów:
vals = np.random.randn(6, 4)
df = pd.DataFrame(vals, index=[0.0, 0.2, 0.3, 0.7, 1.0, 1.3], columns=["A", "B", "C", "D"])
print(df)Dostęp do indeksów obiektu DataFrame możliwy jest przez
właściwość index:
print(df.index)Dostęp do zbioru kolumn poprzez właściwość columns:
print(df.columns)Obie te metody zwracają specjalny obiekt *Index, w
zależności od typu. Możliwa jest łatwa konwersja na Pythonową listę:
list_of_columns = list(df.columns)
for c in list_of_columns:
print(c)Korzystając z właściwości values uzyskamy dostęp do
danych w przechowywanych tabeli w postaci macierzy NumPy:
df.valuesIndeksowanie
Główną przewagą przechowywania danych w postaci
DataFrame w stosunku do zwykłej tablicy
dwuwymiarowej/macierzy jest możliwość dostępu do danych po
przydzielonych etykietach kolumn/wieszy. Poniższe operatory w zależności
od sytuacji zwracają nowy DataFrame będący fragmentem
oryginalnego, obiekt Series (w przypadku pojedynczych
wierszy lub kolumn) lub pojedynczy skalar.
Możliwe jest proste wybranie jednej lub kilku kolumn operatorem
[]:
df["B"]df[["C", "D"]]Lub wierszy:
df[0.2:1.0]Dla uniknięcia możliwych pomyłek (w powyższych przykładach nie widać
jednoznacznie czy wybór odbywa się po kolumnach czy po wierszach) zaleca
się korzystanie z metod loc[] oraz iloc[]:
Indeksowanie po etykietach (loc)
Metoda loc[] pozwala wybrać fragment
DataFrame korzystając z etykiet:
Wybór wiersza o etykiecie (indeksie) 0.7:
df.loc[0.7, :]Wybór kolumn o etykietach “A” oraz “B” (lista etykiet):
df.loc[:, ["A", "B"]]Możliwe jest podawanie również przedziałów i jednoczesny wybór po kolumnach i wierszach:
df.loc[0.2:0.3, "A":"C"]Wybór pojedynczej wartości (skalar):
df.loc[0.2, ["B"]]Indeksowanie po pozycjach (iloc)
Metoda iloc[] pozwala wybrać fragment
DataFrame korzystając z pozycji opisanych
liczbami całkowitymi - na identycznych zasadach jak w przypadku list czy
macierzy NumPy:
Pojedyncze pozycje:
df.iloc[2, 3]Zakresy pozycji:
df.iloc[3:5, 0:2] # wiersze 3,4; kolumny 0,1Listy pozycji:
df.iloc[[1, 2, 4], :] # wiersze 1,2,4, wszystkie kolumnyWybieranie co n-tego wiersza:
df.iloc[0::2, :] # co drugi wiersz, zaczynając od zerowegoIndeksowanie mieszanie
(“loc + iloc”)
Aby posługiwać się jednocześnie etykietami oraz indeksami
porządkowymi (np. nazwami kolumn i numerami wierszy) najlepiej
skonwertować etykiety na indeksy porządkowe i przekazać je do metody
iloc. Przykładowo, jeśli chcemy wybrać wiersze od 0 do 2
włącznie, z kolumny C:
df.iloc[0:3, df.columns.get_loc('C')]Uwaga!
Pojedyncze indeksowanie za pomocą loc lub
iloc zwraca widok na oryginalny
DataFrame, co oznacza, że jeśli zmodyfikujemy znajdujące
się w nim dane, zostaną one zmodyfikowane również w oryginalnym
obiekcie. W przypadku złożonych indeksowań (np.
df.iloc[1:5, ].loc[: 'A']) nie ma takiej gwarancji i w
konsoli zostanie wydrukowane związane z tym ostrzeżenie.
Wybór kolumn za pomocą wyrażenia regularnego
Metoda filter() pozwala na wybór kolumn (lub wierszy po
podaniu opcjonalnego parametru axis) po etykietach, np. za
pomocą dopasowania wyrażeniem regularnym:
df.filter(regex=r"[A-C]")Indeksowanie logiczne
Indeksowanie logiczne pozwala w bardzo szybki sposób wybrać fragmenty tabeli, które spełniają określony warunek. Przykładowo:
df.loc[df["A"] > 0, :] # wybierz wszystkie wiersze, gdzie "A" jest większe od 0Możliwe jest też budowanie bardziej złożonych warunków, na przykład:
df.loc[(df["A"] > -0.75) & (df["B"] < 0.25), :]Modyfikacja zawartości
Po wyborze fragmentu DataFrame możliwe jest przypisanie
nowych wartości wybranej części operatorem przypisania
=.
Przygotujmy nowy zbiór danych:
alpha = np.array([0, np.pi/4, np.pi/2, np.pi*3/4, np.pi])
trig = pd.DataFrame({"sinus": np.round(np.sin(alpha), 10),
"cosinus" : np.round(np.cos(alpha), 10),
"x^2" : alpha**2,
"random" : np.random.randn(len(alpha))}, index=alpha)Możliwe jest przypisanie skalara - wartość zostanie wpisana pod wszystkie pasujące elementy:
trig.loc[1:4, "random"] = 0Lub zbioru nowych wartości, na przykład w postaci macierzy NumPy, przy czym należy pamiętać o podaniu macierzy o odpowiednim rozmiarze:
trig.loc[trig["cosinus"] >= 0, "random"] = np.array([1, 3, 5])Dodawanie kolumn/wierszy
Najprostszym sposobem na dodanie kolumny/wiersza do struktury
DataFrame jest przypisanie wartości do nieistniejącej
kolumny/wiersza, przykładowo:
trig["New column"] = -1trig.loc[1337, :] = -1Zmiana indeksu
Możliwa jest zmiana wartości indeksu wierszy DataFrame
poprzez użycie metody set_index. Przykładowo, jeśli chcemy
wykorzystać wartości znajdujące się w kolumnie sinus jako
indeks:
trig.set_index(trig["sinus"], inplace=True)Zwróć uwagę na dodatkowy parametr inplace - wiele metod
DataFrame domyślnie nie modyfikuje obiektu, na którym je
wywołujemy, tylko zwraca nowy obiekt, co oznacza, że musielibyśmy
zapisać wynik z powrotem do zmiennej:
trig = trig.set_index(trig["sinus"])Dodanie parametru inplace=True powoduje, że operacja
jest wykonywana “w miejscu”. Przy korzystaniu z nowych metod
modyfikujących DataFrame sprawdź w dokumentacji, jakie jest
ich zachowanie.
Zmiana nazw kolumn
Zmiana nazw kolumn możliwa jest w wygodny sposób metodą
rename na podstawie słownika ze zbiorem wpisów
"stara_nazwa": "nowa_nazwa" przekazanego do parametru
columns.
rename_dict = {"sinus": "sin", "cosinus": "cos"}
trig.rename(columns=rename_dict, inplace=True)Analogicznie jak przy set_index metoda możliwa jest
zmiana “w miejscu” lub zwrócenie nowego DataFrame.
Sortowanie
Sortowanie tabeli sprowadza się zazwyczaj do uruchomienia jednej metody, w której podajemy etykietę (lub listę etykiet), po których będą sortowane dane i opcjonalne dodatkowe parametry.
trig.sort_values("cos", axis=0, inplace=True, ascending=False) # sortowanie wzdłuż osi 0 (po wierszach), w miejscu, malejącoOdczyt i zapis do plików
Pandas obsługuje odczyt i zapis danych z wielu różnych formatów.
Jeśli chcemy przenosić dane pommiędzy różnymi programami lub
publikować warto wykorzystać format CSV, ze względu na
prostotę i przenośność.
Eksport do pliku CSV możliwy jest metodą
to_csv(filename) na obiekcie DataFrame:
trig.to_csv("trig.csv")Wczytanie istniejącego pliku odbywa się funkcją
pd.read_csv(filename). Jeśli chcemy, aby kolumna w pliku
została potraktowana jako indeks, musimy przekazać jej położenie do
opcjonalnego argumentu index_col:
trig_from_csv = pd.DataFrame(pd.read_csv("trig.csv", index_col=0)) # read_csv() może zwracać nie tylko DataFrame przekazanie do konstruktora usprawni podpowiadanie składni w IDEJeśli planujemy zachować np. wstępnie przetworzone dane do dalszej
analizy lub wykorzystania w innym programie obsługującym ten format,
warto wykorzystać format HDF5 dedykowany do danych
tabelarycznych. Pozwala on przechowywać bardziej zaawansowane typy
danych, jest także szybszy w parsowaniu i pozwala na opcjonalną
kompresję:
trig.to_hdf("trig.hdf5", "data", format="fixed", mode="w", complevel=5) # zapis
trig_from_hdf = pd.DataFrame(pd.read_hdf("trig.hdf5"))🔥 Zadanie końcowe 🔥
Wczytaj dostarczony plik population_by_country_2019_2020.csv do
DataFrame. (źródło: https://www.kaggle.com/tanuprabhu/population-by-country-2020).Zbadaj zawartość tabeli wyświetlając ją w GUI oraz wyświetlając podsumowanie w konsoli Pythona.
Policz bezwzględną i względną zmianę populacji w 2020 w stosunku do roku 2019 i umieść w nowych kolumnach, odpowiednio
Net population changeiPopulation change [%].Posortuj dane pod kątem względnej zmiany populacji.
Wygeneruj wykres typu “bar” 10 krajów, które miały największy procentowy przyrost populacji. Zawrzyj na nim populacje z 2019 i 2020. Do wyboru kolumn użyj filtra z wyrażeniem regularnym.
Dodaj kolejną kolumnę
Density (2020)i wpisz w niej słowo “Low”.Policz gęstość zaludnienia i w krajach, gdzie przekracza 500 osób na km2 wpisz w kolumnie
Densitysłowo “High”.Wybierz co drugi kraj i zapisz do nowego pliku population_output.csv.
Autorzy: Jakub Tomczyński